﻿using System;
using Tesla.Bounding;
using Tesla.Content;
using Tesla.Core;
using Tesla.Graphics;
using Tesla.Input;
using Tesla.Math;
using Tesla.Scene;
using Tesla.Scene.Extension;
using Tesla.Scene.Shape;

namespace TeslaSamples.Scene {
    [AppDescription("Scene Graph Sample", PlatformCapabilities.Direct3D10 | PlatformCapabilities.XNA4, "SceneGraphImage", "SceneGraphDesc", "SceneGraphSource")]
    public class SceneGraphSample : BasicApp {
        private SpriteFont font;
        private SpriteBatch batch;
        private String msgScaleOn = "Press J to turn off scaling.";
        private String msgScaleOff = "Press J to turn on root node scaling.";
        private bool scaleOn = false;

        /// <summary>
        /// Please see the SceneGraph.rtf description for scene graph organization.
        /// </summary>
        protected override void LoadContent() {
            ClearColor = Color.CornflowerBlue;
            Window.Title = "Scene Graph Sample";

            //Create the teapot sub-tree, we're also creating a small box showing the position of
            //the node in world space
            Node teapotParent = new Node("TeapotParent");
            Box teapotParentVisual = new Box("TeapotParentVisual", 1f, 1f, 1f);
            teapotParentVisual.Material = ContentManager.Load<Material>("LitBasicColor.tem").Clone();
            teapotParent.AddChild(teapotParentVisual);

            //Set a rotate controller onto the teapot parent, so it'll rotate every frame
            teapotParent.AddController(new RotateController(Vector3.UnitY, 25));

            //Create the teapot mesh
            Teapot teapot = new Teapot("Teapot");
            //Load up a default lit color material
            teapot.Material = ContentManager.Load<Material>("LitBasicColor.tem").Clone();
            //Create another rotate controller that will rotate the teapot about its X-Axis
            //45 degrees per second. This shows how we can -easily- concatenate transforms
            //using the hierarchy of the scene graph.
            teapot.AddController(new RotateController(Vector3.UnitX, 45f));

            //Set a red color to the material.
            teapot.Material.SetParameter("MatDiffuse", Color.Crimson.ToVector3());
            teapot.Scale = new Vector3(5f, 5f, 5f);

            //Position and add to parent
            teapot.Translation = new Vector3(-50, 0, 0);
            teapotParent.AddChild(teapot);

            //Add the teapot sub tree to the root.
            RootNode.AddChild(teapotParent);
            
            //Add a white light to the root node
            RootNode.RemoveAllLights();
            PointLight pl = new PointLight();
            pl.Attenuate = false;
            pl.Position = new Vector3(-100, 50, 100);
            RootNode.AddLight(pl);

            //Create the box
            Box box = new Box("Box", 10, 10, 10);
            box.Translation = new Vector3(50, -50, 0);
            box.Material = ContentManager.Load<Material>("LitBasicColor.tem").Clone();
            box.Material.SetParameter("MatDiffuse", Color.Gray.ToVector3());

            //Set the box to only use lights that are attached to it
            box.SceneHints.LightCombineHint = LightCombineHint.Local;

            //Create a green point light
            PointLight pl2 = new PointLight();
            pl2.Attenuate = false;
            pl2.Diffuse = Color.Green;
            pl2.Position = new Vector3(100, 50, 100);
            box.AddLight(pl2);

            //Add the box to the root
            RootNode.AddChild(box);

            //Set a bounding box to be used as the volume for each node/mesh in the scene graph.
            RootNode.SetModelBound(new BoundingBox());

            //Create some input handling to show how you can apply
            //a scaling value to a parent node (this case the scene Root),
            //and see it be applied to all children.
            InputLayer.RegisterTrigger(new InputTrigger(new KeyPressedCondition(Keys.J, false), new InputAction(delegate(GameTime time) {
                if(RootNode.Controllers.Count > 0) {
                    RootNode.RemoveAllControllers();
                    RootNode.SetScale(1.0f);
                    scaleOn = false;
                } else {
                    RootNode.AddController(new ScaleController(1.0f, .1f, 2.0f));
                    scaleOn = true;
                }
            })));

            batch = new SpriteBatch();
            font = ContentManager.Load<SpriteFont>("Fonts//comicsans.fnt");
        }
        
        //Render text saying if we're scaling or not
        protected override void OnRenderPost(IRenderer renderer) {
            batch.Begin();
            Viewport vp = renderer.CurrentCamera.Viewport;
            String text = (scaleOn) ? msgScaleOn + "    " + RootNode.Scale.X.ToString() : msgScaleOff;
            Vector2 pos = new Vector2(10, vp.Height - (font.MeasureString(text).Y + 10));
            batch.DrawString(font, text, pos, Color.White);
            batch.End();
        }

        //This controller will allow us to control the scale of the whole scene
        private class ScaleController : BaseSpatialController {
            private float _scale = 1.0f;
            private float _lowerLimit = .1f;
            private float _upperLimit = 5.0f;
            private int dir = 1;

            public ScaleController(float startScale, float lowerLimit, float upperLimit) {
                _scale = startScale;
                _lowerLimit = lowerLimit;
                _upperLimit = upperLimit;

                if(_upperLimit > _lowerLimit) {
                    dir = 1;
                } else {
                    dir = -1;
                }
            }

            public override void Update(GameTime time, Spatial caller) {
                _scale += (float) time.ElapsedTimeInSeconds * dir;
                if(_scale < _lowerLimit) {
                    _scale = _lowerLimit;
                    dir *= -1;
                } else if(_scale > _upperLimit) {
                    _scale = _upperLimit;
                    dir *= -1;
                }

                caller.SetScale(_scale);
            }
        }
    }
}
